/*
 * acooly.cn Inc.
 * Copyright (c) 2016 All Rights Reserved.
 * create by zhangpu
 * date:2016年4月24日
 *
 */
package com.acooly.module.openapi.client.api.message.impl;

import com.acooly.core.utils.Strings;
import com.acooly.module.openapi.client.api.anotation.ApiMsg;
import com.acooly.module.openapi.client.api.enums.ApiMessageType;
import com.acooly.module.openapi.client.api.message.ApiMessage;
import com.acooly.module.openapi.client.api.message.MessageFactory;
import com.acooly.module.openapi.client.api.message.MessageMeta;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.stereotype.Component;

import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.Map;

/**
 * @author zhangpu
 */
@Component
public class AcoolyAnnotaionScanMessageFactory implements MessageFactory,InitializingBean {

	public Map<String, MessageMeta> metas = Maps.newHashMap();

	private String messagePackage = "classpath*:com.acooly.openapi.client.provider";

	@Override
	public ApiMessage getRequest(String serviceName) {
		return newInstance(metas.get(serviceName).getRequest());
	}

	@Override
	public ApiMessage getResponse(String serviceName) {
		return newInstance(metas.get(serviceName).getResponse());
	}

	@Override
	public ApiMessage getNotify(String serviceName) {
		return newInstance(metas.get(serviceName).getAsyncNotify());
	}

	@Override
	public ApiMessage getReturn(String serviceName) {
		return newInstance(metas.get(serviceName).getSyncNotify());
	}



	@Override
	public void afterPropertiesSet() throws Exception {
		init();
	}


	@SuppressWarnings("unchecked")
	public void init() {
		ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
		try {
			Resource[] resources = resourcePatternResolver.getResources(getResourcePattern());
			ClassLoader loader = this.getClass().getClassLoader();
			String binaryName = null;
			for (Resource resource : resources) {
				binaryName = getCanonicalClassPath(resource);
				try {
					Class<?> clazz = loader.loadClass(binaryName);
					if (Modifier.isAbstract(clazz.getModifiers())) {
						continue;
					}
					ApiMsg apiMsg = getAnotation(clazz);
					if (apiMsg != null) {
						register(apiMsg,(Class<? extends ApiMessage>)clazz);
					}
				} catch (Exception e) {
					continue;
				}
			}
		} catch (Exception e) {
			throw new RuntimeException("扫描Message失败:" + e.getMessage());
		}
	}


	protected void register(ApiMsg apiMsg, Class<? extends ApiMessage> clazz){
		String service = apiMsg.service();
		if(metas.get(service) == null){
			metas.put(service, new MessageMeta());
		}
		if(apiMsg.type() == ApiMessageType.Request){
			metas.get(service).setRequest(clazz);
		}else if(apiMsg.type() == ApiMessageType.Response){
			metas.get(service).setResponse(clazz);
		}else if(apiMsg.type() == ApiMessageType.Return){
			metas.get(service).setSyncNotify(clazz);
		}else if(apiMsg.type() == ApiMessageType.Notify){
			metas.get(service).setAsyncNotify(clazz);
		}
	}


	protected ApiMsg getAnotation(Class<?> clazz) {
		return clazz.getAnnotation(ApiMsg.class);
	}

	protected String getCanonicalClassPath(Resource resource) {
		try {
			URL classFileUrl = resource.getURL();
			String path = classFileUrl.getPath();
			if (Strings.contains(path, "classes/")) {
				path = StringUtils.substringAfter(path, "classes/");
			}
			if (Strings.contains(path, "jar!/")) {
				path = StringUtils.substringAfter(path, "jar!/");
			}
			path = path.replaceAll("/", ".").replace(".class", "");
			return path;
		} catch (Exception e) {
			throw new RuntimeException(e.getMessage());
		}
	}

	private String getResourcePattern() {
		return Strings.replace(messagePackage, ".", "/") + "/**/*.class";
	}

	private <T> T newInstance(Class<T> clazz) {
		try {
			return (T) clazz.newInstance();
		} catch (InstantiationException e) {
			throw new RuntimeException("InstantiationException:" + clazz);
		} catch (IllegalAccessException e) {
			throw new RuntimeException("IllegalAccessException:" + clazz);
		}
	}

	public void setMessagePackage(String messagePackage) {
		this.messagePackage = messagePackage;
	}

}
